package com.appspot.codelab.docs.server.repository;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


import com.appspot.codelab.docs.client.repository.RepositoryAuthorizationException;
import com.appspot.codelab.docs.client.repository.RepositoryProject;
import com.appspot.codelab.docs.client.repository.RepositoryService;
import com.appspot.codelab.docs.client.repository.RepositoryServiceException;
import com.appspot.codelab.docs.client.repository.RepositoryUser;
import com.appspot.codelab.docs.server.UserProject;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.repackaged.com.google.common.util.Base64;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The server side implementation of the GData RPC service.
 */
@SuppressWarnings("serial")
public class RepositoryServiceImpl extends RemoteServiceServlet implements
    RepositoryService {
	
  /**
   * The path to redirect to upon logout.
   */
  public static final String LOGOUT_RETURN_RELATIVE_PATH = "/";
	  
  /**
   * Retrieves the currently signed on user.
   */
  @Override
  public RepositoryUser getUser() {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null){
      RepositoryUser ruser = new RepositoryUser();
      ruser.setEmail(user.getEmail());
      ruser.setUserId(user.getUserId());
      ruser.setNickname(user.getNickname());
      return ruser;
    }
    return null;
  }
  
  /**
   * Ends the current user's session.
   */
  @Override
  public String logout() throws RepositoryServiceException {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null){
      try {
        URI url = new URI(this.getThreadLocalRequest().getRequestURL().toString());
        return userService.createLogoutURL("http://" + url.getAuthority() + LOGOUT_RETURN_RELATIVE_PATH);
      } catch(Exception e) {
    	e.printStackTrace();
    	throw new RepositoryServiceException(e.getMessage());
      }
    }
    return "/";
  }

  public boolean projectExists(String project) {
	try {
	  getSourceDirectory(project, "/", null);
	  return true;
	} catch (RepositoryServiceException x) {
	  return false;
	}
  }
  
  @Override
  public String getSourceDirectory(String project, String path, String revision) throws RepositoryServiceException {
	try {
	  if (path == null || path.equals("")) {
		path = "/";
	  }
	  project = URLEncoder.encode(project, "UTF-8");
	  path = URLEncoder.encode(path, "UTF-8");
      URL url = new URL("http://code.google.com/p/" + project + "/source/dirfeed?p=" + path + (revision != null ? "&r=" + revision : ""));
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(0);
      conn.setReadTimeout(0);
      
      System.out.println("Http Status: " + conn.getResponseCode());
      System.out.println("Http Message: " + conn.getResponseMessage());
      if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        StringBuffer sb = new StringBuffer();
        String line;
        while ((line = reader.readLine()) != null) {
          sb.append(line);
        }
        reader.close();
        String resp = sb.toString();
        System.out.println(project + ":" + path + ":" + resp);
        if (resp.contains("\"error\":\"FAILURE\"")) {
          return null;
        }
        return resp;
      } else {
    	throw new RepositoryServiceException("Invalid path.");
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    } catch (IOException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    }
  }

  @Override
  public String getSourceFileContents(String project, String path, String revision) throws RepositoryServiceException {
	try {
	  if (!path.startsWith("/")) {
		path = "/" + path;
	  }
	  URL url;
	  if (revision == null) {
	    url = new URL("http://" + project + ".googlecode.com/svn" + path);
	  } else {
		url = new URL("http://" + project + ".googlecode.com/svn-history/" + revision + path);
	  }
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(0);
      conn.setReadTimeout(0);

      System.out.println("Http Status: " + conn.getResponseCode());
      System.out.println("Http Message: " + conn.getResponseMessage());
      if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        StringBuffer sb = new StringBuffer();
        String line;
        while ((line = reader.readLine()) != null) {
          sb.append(line + "\n");
        }
        reader.close();
        return sb.toString();
      } else {
    	throw new RepositoryServiceException("Invalid path.");
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    } catch (IOException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    }
  }

  @Override
  public boolean uploadSourceFile(String project, String path, String username, String password,
	    String contents) throws RepositoryServiceException, RepositoryAuthorizationException {
    try {
      if (username.endsWith("@gmail.com")) {
    	username = username.substring(0, username.length() - 10);
      }
	  if (!path.startsWith("/")) {
		path = "/" + path;
	  }
      String auth = Base64.encode((username + ":" + password).getBytes("UTF-8"));
      URL url = new URL("https://" + project + ".googlecode.com/svn" + path);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(0);
      conn.setReadTimeout(0);
      conn.setInstanceFollowRedirects(false);
      conn.setDoOutput(true);
      conn.setRequestMethod("PUT");
      conn.addRequestProperty("Authorization", "Basic " + auth);
      conn.addRequestProperty("User-Agent", "Code Lab");

      OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
      writer.write(contents);
      writer.close();
  
      System.out.println("Http Status: " + conn.getResponseCode());
      System.out.println("Http Message: " + conn.getResponseMessage());
      if (conn.getResponseCode() == HttpURLConnection.HTTP_OK ||
    	  conn.getResponseCode() == HttpURLConnection.HTTP_CREATED ||
    	  conn.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
    	return true;
      } else if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
    	throw new RepositoryAuthorizationException("Invalid resource credentials.");
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    } catch (IOException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    }
    return false;
  }
  
  @Override
  public boolean deleteSourceFile(String project, String path, String username, String password) throws RepositoryServiceException, RepositoryAuthorizationException {
    try {
      if (username.endsWith("@gmail.com")) {
    	username = username.substring(0, username.length() - 10);
      }
	  if (!path.startsWith("/")) {
		path = "/" + path;
	  }
      String auth = Base64.encode((username + ":" + password).getBytes("UTF-8"));
      URL url = new URL("https://" + project + ".googlecode.com/svn" + path);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(0);
      conn.setReadTimeout(0);
      conn.setInstanceFollowRedirects(false);
      conn.setRequestMethod("DELETE");
      conn.addRequestProperty("Authorization", "Basic " + auth);
      conn.addRequestProperty("User-Agent", "Code Lab");
  
      System.out.println("Http Status: " + conn.getResponseCode());
      System.out.println("Http Message: " + conn.getResponseMessage());
      if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
    	return true;
      } else if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
    	throw new RepositoryAuthorizationException("Invalid resource credentials.");
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    } catch (IOException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    }
    return false;
  }
  
  @Override
  public boolean renameSourceFile(String project, String path, String newPath, String username, String password)
      throws RepositoryServiceException, RepositoryAuthorizationException {
	String contents = getSourceFileContents(project, path, null);
	uploadSourceFile(project, newPath, username, password, contents);
	deleteSourceFile(project, path, username, password);
	return true;
  }
  
  @Override
  public boolean uploadProjectFile(String project, String path, String username, String password,
			String contents, String summary, String[] labels) throws RepositoryServiceException, RepositoryAuthorizationException {
    try {
      if (username.endsWith("@gmail.com")) {
    	username = username.substring(0, username.length() - 10);
      }
      String boundary = "----------Googlecode_boundary_reindeer_flotilla";
      String auth = Base64.encode((username + ":" + password).getBytes("UTF-8"));
      HashMap<String, String> fields = new HashMap<String, String>();
      fields.put("summary", summary);
      URL url = new URL("https://" + project + ".googlecode.com/files");
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(0);
      conn.setReadTimeout(0);
      conn.setDoOutput(true);
      conn.setRequestMethod("POST");
      conn.addRequestProperty("Authorization", "Basic " + auth);
      conn.addRequestProperty("User-Agent", "Code Lab");
      conn.addRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
      String body = encodeUploadRequest(fields, path, contents, boundary);

      OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
      writer.write(body);
      writer.close();
  
      System.out.println("Http Status: " + conn.getResponseCode());
      if (conn.getResponseCode() == HttpURLConnection.HTTP_OK ||
    	  conn.getResponseCode() == HttpURLConnection.HTTP_CREATED) {
        return true;
      } else if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
    	throw new RepositoryAuthorizationException("Invalid resource credentials.");
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    } catch (IOException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    }
    return true;
  }

  @Override
  public boolean createSourceDirectory(String project, String path,
		String username, String password) throws RepositoryServiceException, RepositoryAuthorizationException {
    try {
      if (username.endsWith("@gmail.com")) {
    	username = username.substring(0, username.length() - 10);
      }
	  if (!path.startsWith("/")) {
		path = "/" + path;
	  }
      String auth = Base64.encode((username + ":" + password).getBytes("UTF-8"));
      URL url = new URL("https://" + project + ".googlecode.com/svn" + path);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(0);
      conn.setReadTimeout(0);
      conn.setInstanceFollowRedirects(false);
      conn.setRequestMethod("PUT");
      conn.addRequestProperty("Authorization", "Basic " + auth);
      conn.addRequestProperty("User-Agent", "Code Lab");
  
      System.out.println("Http Status: " + conn.getResponseCode());
      System.out.println("Http Message: " + conn.getResponseMessage());
      if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
    	return true;
      } else if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
    	throw new RepositoryAuthorizationException("Invalid resource credentials.");
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    } catch (IOException e) {
      e.printStackTrace();
      throw new RepositoryServiceException(e.getMessage());
    }
    return false;
  }
  
  private String encodeUploadRequest(HashMap<String, String> fields,
	    String fileName, String fileContents, String boundary) {
	String newLine = "\r\n";
	String body = "";
	for (Map.Entry<String, String> entry : fields.entrySet()) {
	  body += "--" + boundary + newLine;
	  body += "Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + newLine;
	  body += newLine;
	  body += entry.getValue() + newLine;
	}
	body += "--" + boundary + newLine;
	body += "Content-Disposition: form-data; name=\"filename\"; filename=\"" + fileName + "\"" + newLine;
	body += "Content-Type: application/octet-stream" + newLine;
	body += newLine;
	body += fileContents + newLine;
	body += "--" + boundary + "--" + newLine;
	body += newLine;
	return body;
  }


  @Override
  public RepositoryProject getProject(String projectName) {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null){
      UserProject proj = UserProject.getUserProject(projectName, user.getEmail());
      if (proj != null) {
    	proj.setActivity(new Date());
    	RepositoryProject rproj = new RepositoryProject();
    	rproj.setProjectName(proj.getProject());
    	rproj.setProjectToken(proj.getToken());
    	return rproj;
      }
    }
    return null;
  }
  

  @Override
  public RepositoryProject putProject(String projectName, String projectToken) {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null){
      UserProject proj = UserProject.storeUserProject(
          projectName, user.getEmail(), projectToken);
  	  RepositoryProject rproj = new RepositoryProject();
	  rproj.setProjectName(proj.getProject());
	  rproj.setProjectToken(proj.getToken());
      return rproj;
    }
    return null;
  }

}
